﻿using System;
using System.Linq;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Management;
using System.Threading;

using PickGold.Data.Hsts.General.Extensions;
using PickGold.Data.Hsts.Records;
using PickGold.Data.Hsts.Storage;
using PickGold.Data.Hsts.WaterfallTree;
using PickGold.Data.Hsts.General.IO;

namespace PickGold.Data.Hsts.Database
{
	public class StorageEngine : WTree, IStorageEngine
	{
		//user scheme
		private Dictionary<string, Item1> map = new Dictionary<string, Item1>();

		private readonly object SyncRoot = new object();

		public StorageEngine(IHeap heap)
			: base(heap)
		{
			foreach (var locator in GetAllLocators())
			{
				if (locator.IsDeleted)
					continue;

				Item1 item = new Item1(locator, null);

				map[locator.Name] = item;
			}
		}

		private Item1 Obtain(string name, int structureType, DataType keyDataType, DataType recordDataType, Type keyType, Type recordType)
		{
			Debug.Assert(keyDataType != null);
			Debug.Assert(recordDataType != null);

			Item1 item;
			if (!map.TryGetValue(name, out item))
			{
				if (keyType == null)
					keyType = DataTypeUtils.BuildType(keyDataType);
				if (recordType == null)
					recordType = DataTypeUtils.BuildType(recordDataType);

				var locator = CreateLocator(name, structureType, keyDataType, recordDataType, keyType, recordType);
				XTable table = new XTable(this, locator);

				map[name] = item = new Item1(locator, table);
			}
			else
			{
				var locator = item.Locator;

				if (locator.StructureType != structureType)
					throw new ArgumentException(String.Format("Invalid structure type for '{0}'", name));

				if (keyDataType != locator.KeyDataType)
					throw new ArgumentException("keyDataType");

				if (recordDataType != locator.RecordDataType)
					throw new ArgumentException("recordDataType");

				if (locator.KeyType == null)
					locator.KeyType = DataTypeUtils.BuildType(keyDataType);
				else
				{
					if (keyType != null && keyType != locator.KeyType)
						throw new ArgumentException(String.Format("Invalid keyType for table '{0}'", name));
				}

				if (locator.RecordType == null)
					locator.RecordType = DataTypeUtils.BuildType(recordDataType);
				else
				{
					if (recordType != null && recordType != locator.RecordType)
						throw new ArgumentException(String.Format("Invalid recordType for table '{0}'", name));
				}

				locator.AccessTime = DateTime.Now;
			}

			if (!item.Locator.IsReady)
				item.Locator.Prepare();

			if (item.Table == null)
				item.Table = new XTable(this, item.Locator);

			return item;
		}

		#region IStorageEngine

		public ITable<IData, IData> OpenXTable(string name, DataType keyDataType, DataType recordDataType)
		{
			if (!Monitor.TryEnter(SyncRoot, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(SyncRoot);
			try
			{
				var item = Obtain(name, StructureType.XTABLE, keyDataType, recordDataType, null, null);
				return item.Table;
			}
			finally
			{
				Monitor.Exit(SyncRoot);
			}
		}

		public ITable<TKey, TRecord> OpenXTable<TKey, TRecord>(string name)
		{
			if (!Monitor.TryEnter(SyncRoot, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(SyncRoot);
			try
			{
				Type keyType = typeof(TKey);
				Type recordType = typeof(TRecord);

				DataType keyDataType = DataTypeUtils.BuildDataType(keyType);
				DataType recordDataType = DataTypeUtils.BuildDataType(recordType);

				var item = Obtain(name, StructureType.XTABLE, keyDataType, recordDataType, keyType, recordType);

				if (item.Direct == null)
					item.Direct = new XTable<TKey, TRecord>(item.Table);

				return (XTable<TKey, TRecord>)item.Direct;
			}
			finally
			{
				Monitor.Exit(SyncRoot);
			}
		}

		public ITable<TKey, TRecord> OpenXTablePortable<TKey, TRecord>(string name, ITransformer<TKey, IData> keyTransformer = null, ITransformer<TRecord, IData> recordTransformer = null)
		{
			if (!Monitor.TryEnter(SyncRoot, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(SyncRoot);
			try
			{
				Type keyType = typeof(TKey);
				Type recordType = typeof(TRecord);

				DataType keyDataType = DataTypeUtils.BuildDataType(keyType);
				DataType recordDataType = DataTypeUtils.BuildDataType(recordType);

				var item = Obtain(name, StructureType.XTABLE, keyDataType, recordDataType, null, null);

				if (item.Portable == null)
					item.Portable = new XTablePortable<TKey, TRecord>(item.Table, keyTransformer, recordTransformer);

				return (ITable<TKey, TRecord>)item.Portable;
			}
			finally
			{
				Monitor.Exit(SyncRoot);
			}
		}

		public XFile OpenXFile(string name)
		{
			if (!Monitor.TryEnter(SyncRoot, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(SyncRoot);
			try
			{
				var item = Obtain(name, StructureType.XFILE, DataType.Int64, DataType.ByteArray, typeof(long), typeof(byte[]));

				if (item.File == null)
					item.File = new XFile(item.Table);

				return item.File;
			}
			finally
			{
				Monitor.Exit(SyncRoot);
			}
		}

		public IDescriptor this[string name]
		{
			get
			{
				if (!Monitor.TryEnter(SyncRoot, Fiber.LockTimeout))
					Fiber.ThrowLockTimeoutException(SyncRoot);
				try
				{
					Item1 item;
					if (!map.TryGetValue(name, out item))
						return null;

					return item.Locator;
				}
				finally
				{
					Monitor.Exit(SyncRoot);
				}
			}
		}

		public IDescriptor Find(long id)
		{
			if (!Monitor.TryEnter(SyncRoot, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(SyncRoot);
			try
			{
				return GetLocator(id);
			}
			finally
			{
				Monitor.Exit(SyncRoot);
			}
		}

		public void Delete(string name)
		{
			if (!Monitor.TryEnter(SyncRoot, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(SyncRoot);
			try
			{
				Item1 item;
				if (!map.TryGetValue(name, out item))
					return;

				map.Remove(name);

				if (item.Table != null)
				{
					item.Table.Clear();
					item.Table.Flush();
				}

				item.Locator.IsDeleted = true;
				return;
			}
			finally
			{
				Monitor.Exit(SyncRoot);
			}
		}

		public void Rename(string name, string newName)
		{
			if (!Monitor.TryEnter(SyncRoot, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(SyncRoot);
			try
			{
				if (map.ContainsKey(newName))
					return;

				Item1 item;
				if (!map.TryGetValue(name, out item))
					return;

				item.Locator.Name = newName;

				map.Remove(name);
				map.Add(newName, item);
				return;
			}
			finally
			{
				Monitor.Exit(SyncRoot);
			}
		}

		public bool Exists(string name)
		{
			if (!Monitor.TryEnter(SyncRoot, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(SyncRoot);
			try
			{
				return map.ContainsKey(name);
			}
			finally
			{
				Monitor.Exit(SyncRoot);
			}
		}

		public int Count
		{
			get
			{
				if (!Monitor.TryEnter(SyncRoot, Fiber.LockTimeout))
					Fiber.ThrowLockTimeoutException(SyncRoot);
				try
				{
					return map.Count;
				}
				finally
				{
					Monitor.Exit(SyncRoot);
				}
			}
		}

		public long DatabaseSize
		{
			get
			{
				return Heap.Size;
			}
		}

		public override void Commit()
		{
			if (!Monitor.TryEnter(SyncRoot, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(SyncRoot);
			try
			{
				foreach (var kv in map)
				{
					var table = kv.Value.Table;

					if (table != null)
					{
						if (table.IsModified)
							table.Locator.ModifiedTime = DateTime.Now;

						table.Flush();
					}
				}

				base.Commit();

				foreach (var kv in map)
				{
					var table = kv.Value.Table;

					if (table != null)
						table.IsModified = false;
				}
				return;
			}
			finally
			{
				Monitor.Exit(SyncRoot);
			}
		}

		public override void Close()
		{
			base.Close();
		}

		public IEnumerator<IDescriptor> GetEnumerator()
		{
			if (!Monitor.TryEnter(SyncRoot, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(SyncRoot);
			try
			{
				return map.Select(x => (IDescriptor)x.Value.Locator).GetEnumerator();
			}
			finally
			{
				Monitor.Exit(SyncRoot);
			}
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion

		private class Item1
		{
			public Locator Locator;
			public XTable Table;

			public ITable Direct;
			public ITable Portable;
			public XFile File;

			public Item1(Locator locator, XTable table)
			{
				Locator = locator;
				Table = table;
			}
		}
	}
}
